﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using de.rwth.rz.campus.sharepointutils.UsersAndGroups;
using System.Net.Mail;

namespace de.rwth.rz.campus.sharepointutils.EMail
{
    /// <remarks>
    /// 
    /// Copyright 2010 RWTH Aachen, CAMPUS Labs
    /// 
    /// Licensed under the Apache License, Version 2.0 (the "License");
    /// you may not use this file except in compliance with the License.
    /// You may obtain a copy of the License at
    ///
    /// http://www.apache.org/licenses/LICENSE-2.0
    ///
    /// Unless required by applicable law or agreed to in writing, software
    /// distributed under the License is distributed on an "AS IS" BASIS,
    /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    /// See the License for the specific language governing permissions and
    /// limitations under the License.
    /// 
    /// This class can be used to send emails to SharePoint groups.
    /// For every SharePoint user in a SharePoint group an email is generated.
    /// Be aware of the load of your SMTP server.
    /// 
    /// </remarks>
    public class GroupMail
    {
        private MailAddress from = null;
        private List<SPGroup> spGroups = new List<SPGroup>();
        private Boolean isHTML = true;
        private String subject = String.Empty;
        private String message = String.Empty;
        private String smtpServerAddress = String.Empty;
        private int smtpServerPort = 25;
        private MailPriority priority = MailPriority.Normal;
        private List<String> logMessages = new List<String>();
        private List<String> userWithNonValidMail = new List<String>();
        private List<String> otherMailAddresses = new List<String>();
        private int mailsSent = 0;
        private int mailsWithError = 0;

        /// <summary>
        /// Initialize a new group mail, where the "From"-User is set.
        /// If the user has no valid email-address the address "noreply@localhost" is used.
        /// </summary>
        /// <param name="spUser">The SharePoint user, which sends the mail.</param>
        /// <example>This example shows, how to use the current user as a sender.
        /// <code>
        /// GroupMail gm = new GroupMail(SPContext.Current.Web.CurrentUser);
        /// </code>
        /// </example>
        public GroupMail(SPUser spUser)
        {
            String username = String.Empty;
            String hostname = "localhost";
            try
            {
                hostname = System.Environment.MachineName;
            }
            catch (InvalidOperationException)
            {
                //not important
            }
            String eMailAddress = "noreply@" + hostname;
            if (spUser != null)
            {
                if (EMail.IsValidEMailAddress(spUser.Email))
                {
                    eMailAddress = spUser.Email;
                }
                if (!String.IsNullOrEmpty(spUser.Name))
                {
                    username = spUser.Name;
                }
            }
            if (!String.IsNullOrEmpty(username))
            {
                from = new MailAddress(eMailAddress, username);
            }
            else
            {
                from = new MailAddress(eMailAddress);
            }
        }

        /// <summary>
        /// The sender of the groupmail
        /// </summary>
        public MailAddress From
        {
            get { return from; }
            set { from = value; }
        }

        /// <summary>
        /// Get all log messages
        /// </summary>
        public List<String> LogMessages
        {
            get { return logMessages; }
        }

        /// <summary>
        /// True, if the mail-messages are HTML encoded (default)
        /// </summary>
        public Boolean IsHTML
        {
            get { return isHTML; }
            set { isHTML = value; }
        }

        /// <summary>
        /// The subject of the groupmail
        /// </summary>
        public String Subject
        {
            get { return subject; }
            set { subject = value; }
        }

        /// <summary>
        /// The message of the groupmail
        /// </summary>
        public String Message
        {
            get { return message; }
            set { message = value; }
        }

        /// <summary>
        /// The IP-Address or FQDN of the SMTP server 
        /// </summary>
        public String SMTPServerAddress
        {
            get { return smtpServerAddress; }
            set { smtpServerAddress = value; }
        }

        /// <summary>
        /// The port number of the SMTP Server, default is 25
        /// </summary>
        public int SMTPServerPort
        {
            get { return smtpServerPort; }
            set { smtpServerPort = value; }
        }

        /// <summary>
        /// The priority of the groupmail, default is "normal"
        /// </summary>
        public MailPriority Priority
        {
            get { return priority; }
            set { priority = value; }
        }

        /// <summary>
        /// Tries to use the SMTP Server from the SharePoint central administration.
        /// </summary>
        /// <param name="spWebApplicationCollection">The SharePoint webapplication collection</param>
        /// <returns>True, if the SMTP Server could be found</returns>
        /// <example>
        /// <code>
        /// bool isSet = SetSPSMTPServer(SPWebService.ContentService.WebApplications);
        /// </code>
        /// </example>
        public Boolean SetSPSMTPServer(SPWebApplicationCollection spWebApplicationCollection)
        {
            String smtpServerAddress = null;
            SPOutboundMailServiceInstance smtpServer = new SPOutboundMailServiceInstance();
            if (spWebApplicationCollection != null)
            {
                foreach (SPWebApplication spWebApplication in spWebApplicationCollection)
                {
                    smtpServer = spWebApplication.OutboundMailServiceInstance;
                    smtpServerAddress = smtpServer.Server.Address;
                    if (String.IsNullOrEmpty(smtpServerAddress))
                    {
                        continue;
                    }
                    else
                    {
                        this.smtpServerAddress = smtpServerAddress;
                        break;
                    }
                }
            }
            return !String.IsNullOrEmpty(smtpServerAddress);
        }

        /// <summary>
        /// Adds a SharePoint group to the recievers of the groupmail
        /// </summary>
        /// <param name="spGroup">The SharePoint group</param>
        public void AddGroup(SPGroup spGroup)
        {
            spGroups.Add(spGroup);
        }

        /// <summary>
        /// Gets the list of groups that has been added to the groupmail
        /// </summary>
        /// <returns>The list of groups that will recieve the groupmail</returns>
        public List<SPGroup> GetAddedGroups()
        {
            return this.spGroups;
        }

        /// <summary>
        /// Adds an email-address to the recievers
        /// </summary>
        /// <param name="eMailAddress">One email-address of the user</param>
        public void AddOther(String eMailAddress)
        {
            this.otherMailAddresses.Add(eMailAddress);
        }

        /// <summary>
        /// Gets the list of other recievers
        /// </summary>
        /// <returns>A list of email-strings</returns>
        public List<String> GetOthers()
        {
            return this.otherMailAddresses;
        }

        /// <summary>
        /// Returns the count of the mails that has been sent successfully
        /// </summary>
        public int MailsSent
        {
            get { return mailsSent; }
        }

        /// <summary>
        /// Returns the count of the mails that could not be sent
        /// </summary>
        public int MailsWithError
        {
            get { return mailsWithError; }
        }

        /// <summary>
        /// Sends the mail to the given groups
        /// </summary>
        /// <param name="website">The current website</param>
        /// <returns>The log entry of the sent mails</returns>
        /// <exception cref="System.Exception"/>
        public String SendGroupMail(SPWeb website)
        {
            int sent = 0;
            Boolean everythingIsFine = true;
            List<SPUser> allUsers = new List<SPUser>();
            //Log Groups
            AddLogEntry(Properties.Resources.GROUP_COUNT + ": " + this.spGroups.Count);
            foreach (SPGroup spGroup in this.spGroups)
            {
                List<SPUser> spUsers = Groups.GetUsersBy(spGroup,website);
                if (spUsers != null) allUsers.AddRange(spUsers);
            }
            //Log Users
            AddLogEntry(Properties.Resources.USER_COUNT_ALL + ": " + allUsers.Count);
            AddLogEntry(Properties.Resources.USER_COUNT_DISTINCT + ": " + allUsers.Distinct(new UsersAndGroups.SPUserComparer()).Count());
            AddLogEntry(Properties.Resources.USER_COUNT_OTHERS + ": " + otherMailAddresses.Count);
            //Send one mail to every SharePoint user
            SendMailToSPUsers(ref sent, ref everythingIsFine, allUsers);
            //Send one mail to the cc users
            SendMailToOtherUsers(ref sent, ref everythingIsFine);
            //Log invalid mail user
            StringBuilder nonValidMessage = null;
            if (userWithNonValidMail.Count > 0)
            {
                nonValidMessage = new StringBuilder(userWithNonValidMail.Count + " [");
                foreach (String nonValidMail in this.userWithNonValidMail)
                {
                    nonValidMessage.Append(" ");
                    nonValidMessage.Append(nonValidMail);
                    nonValidMessage.Append(" ");
                }
                nonValidMessage.Append("]");
            }
            else
            {
                nonValidMessage = new StringBuilder(userWithNonValidMail.Count.ToString());
            }
            AddLogEntry(Properties.Resources.WITHOUT_VALID_MAIL_COUNT + ": " + nonValidMessage.ToString());
            AddLogEntry(Properties.Resources.SENT_MAIL + ": " + sent);
            //Build log
            StringBuilder logEntries = new StringBuilder(String.Empty);
            foreach (String s in this.logMessages)
            {
                logEntries.AppendLine(s);
            }
            //Throw exception if an error has occured
            if (!everythingIsFine) throw new Exception(Properties.Resources.NOT_EVERY_MAIL_SEND);
            return logEntries.ToString();
        }

        private void SendMailToOtherUsers(ref int sent, ref Boolean everythingIsFine)
        {
            foreach (String mailAddress in this.otherMailAddresses)
            {
                EMail email = new EMail();
                if (EMail.IsValidEMailAddress(mailAddress))
                {
                    try
                    {
                        email.AddToReciever(mailAddress, null);
                        if (from != null) email.From = this.from;
                        email.IsHTML = this.isHTML;
                        email.Message = this.message;
                        email.SMTPServerAddress = this.smtpServerAddress;
                        email.SMTPServerPort = this.smtpServerPort;
                        email.Subject = this.subject;
                        email.Priority = this.priority;
                        email.SendMail();
                        sent++;
                        this.mailsSent++;
                    }
                    catch (Exception e)
                    {
                        this.mailsWithError++;
                        AddLogEntry(Properties.Resources.ERROR + " <" + mailAddress + ">: " + e.Message);
                        everythingIsFine = false;
                    }
                }
                else
                {
                    this.mailsWithError++;
                    userWithNonValidMail.Add("<" + mailAddress + ">");
                }
            }
        }

        private void SendMailToSPUsers(ref int sent, ref Boolean everythingIsFine, List<SPUser> allUsers)
        {
            foreach (SPUser spuser in allUsers.Distinct(new UsersAndGroups.SPUserComparer()))
            {
                EMail email = new EMail();
                if (EMail.IsValidEMailAddress(spuser.Email))
                {
                    try
                    {
                        email.AddToReciever(spuser.Email, spuser.Name);
                        if (from != null) email.From = this.from;
                        email.IsHTML = this.isHTML;
                        email.Message = this.message;
                        email.SMTPServerAddress = this.smtpServerAddress;
                        email.SMTPServerPort = this.smtpServerPort;
                        email.Subject = this.subject;
                        email.Priority = this.priority;
                        email.SendMail();
                        sent++;
                        this.mailsSent++;
                    }
                    catch (Exception e)
                    {
                        mailsWithError++;
                        AddLogEntry(Properties.Resources.ERROR + " <" + spuser.Email + ">: " + e.Message);
                        everythingIsFine = false;
                    }
                }
                else
                {
                    this.mailsWithError++;
                    StringBuilder userAndEMail = new StringBuilder(String.Empty);
                    userAndEMail.Append("(");
                    if (!String.IsNullOrEmpty(spuser.Name)) userAndEMail.Append(spuser.Name);
                    userAndEMail.Append(")");
                    userAndEMail.Append("<");
                    if (!String.IsNullOrEmpty(spuser.Email)) userAndEMail.Append(spuser.Email);
                    userAndEMail.Append(">");
                    userWithNonValidMail.Add(userAndEMail.ToString());
                }
            }
        }

        /// <summary>
        /// Adds an entry to the log
        /// </summary>
        /// <param name="message">The message that is added to the log</param>
        private void AddLogEntry(String message)
        {
            this.logMessages.Add(message);
        }
    }
}
